package com.cloudeggtech.granite.sdk;

import com.cloudeggtech.basalt.protocol.core.IError;
import com.cloudeggtech.basalt.protocol.core.ProtocolChain;
import com.cloudeggtech.basalt.protocol.core.stanza.Iq;
import com.cloudeggtech.basalt.protocol.core.stream.Bind;
import com.cloudeggtech.basalt.protocol.core.stream.Feature;
import com.cloudeggtech.basalt.protocol.core.stream.Features;
import com.cloudeggtech.basalt.protocol.core.stream.Session;
import com.cloudeggtech.basalt.protocol.oxm.IOxmFactory;
import com.cloudeggtech.basalt.protocol.oxm.OxmService;
import com.cloudeggtech.basalt.protocol.oxm.annotation.AnnotatedParserFactory;
import com.cloudeggtech.basalt.protocol.oxm.parsers.SimpleObjectParserFactory;
import com.cloudeggtech.basalt.protocol.oxm.parsers.core.stream.BindParser;
import com.cloudeggtech.basalt.protocol.oxm.translators.core.stream.BindTranslatorFactory;
import com.cloudeggtech.chalk.core.stream.IAuthenticationCallback;
import com.cloudeggtech.chalk.core.stream.IAuthenticationToken;
import com.cloudeggtech.chalk.core.stream.INegotiationContext;
import com.cloudeggtech.chalk.core.stream.IStreamNegotiant;
import com.cloudeggtech.chalk.core.stream.NegotiationException;
import com.cloudeggtech.chalk.core.stream.StandardStreamConfig;
import com.cloudeggtech.chalk.core.stream.StandardStreamer;
import com.cloudeggtech.chalk.core.stream.negotiants.InitialStreamNegotiant;
import com.cloudeggtech.chalk.core.stream.negotiants.ResourceBindingNegotiant;
import com.cloudeggtech.chalk.core.stream.negotiants.SessionEstablishmentNegotiant;
import com.cloudeggtech.chalk.core.stream.negotiants.tls.IPeerCertificateTruster;
import com.cloudeggtech.chalk.core.stream.negotiants.tls.TlsNegotiant;
import com.cloudeggtech.chalk.network.ConnectionException;

import java.util.ArrayList;
import java.util.List;

public class GIMStreamer extends StandardStreamer {

    private static StandardStreamConfig config;

    public GIMStreamer(StandardStreamConfig config) {
        super(config, null);
        this.config = config;
    }
//    public static String name;

    private IPeerCertificateTruster certificateTruster;

    private IAuthenticationToken authToken;

    @Override
    public void negotiate(IAuthenticationToken authToken) {
        this.authToken = authToken;
        super.negotiate(authToken);
    }

    @Override
    protected List<IStreamNegotiant> createNegotiants() {
        List<IStreamNegotiant> negotiants = new ArrayList<>();

        negotiants.add(new InitialStreamNegotiant(config.getHost(), config.getLang()));
        TlsNegotiant tls = new TlsNegotiant(config.getHost(), config.getLang(), config.isTlsPreferred());
        tls.setPeerCertificateTruster(certificateTruster);
        negotiants.add(tls);

        AndroidSaslNegotiant sasl = new AndroidSaslNegotiant(config.getHost(), config.getLang(), authToken);
        sasl.setAuthenticationCallback(getAuthenticationCallback());

        negotiants.add(sasl);

        negotiants.add(new ResourceBindingNegotiant(config.getResource()));
        negotiants.add(new SessionEstablishmentNegotiant());

        return negotiants;
    }

    @Override
    public void setPeerCertificateTruster(IPeerCertificateTruster certificateTruster) {
        this.certificateTruster = certificateTruster;
    }

    private static class InitialStreamNegotiantTestImpl extends InitialStreamNegotiant {

        static {
            oxmFactory.register(ProtocolChain.first(Features.PROTOCOL).next(Bind.PROTOCOL),
                    new AnnotatedParserFactory<Bind>(BindParser.class));
            oxmFactory.register(ProtocolChain.first(Features.PROTOCOL).next(Session.PROTOCOL),
                    new SimpleObjectParserFactory<Session>(Session.PROTOCOL, Session.class));
        }

        public InitialStreamNegotiantTestImpl(String hostName, String lang) {
            super(hostName, lang);
        }
    }

    private static class ResourceBindNegotiantTestImpl extends ResourceBindingNegotiant {

        private static IOxmFactory processingFactory = OxmService.createMinimumOxmFactory();

        private String resource;

        static {
            processingFactory.register(ProtocolChain.first(Iq.PROTOCOL).next(Bind.PROTOCOL),
                    new AnnotatedParserFactory<Bind>(BindParser.class)
            );

            processingFactory.register(Bind.class, new BindTranslatorFactory());
        }


        public ResourceBindNegotiantTestImpl(String resource) {
            super(resource);
        }

        protected void doNegotiate(INegotiationContext context) throws ConnectionException, NegotiationException {
            @SuppressWarnings("unchecked")
            List<Feature> features = (List<Feature>) context.getAttribute(
                    StandardStreamer.NEGOTIATION_KEY_FEATURES);
            Bind bind = findBind(features);

            if (bind != null) {
                negotiateBind(context);
            }
        }

        private void negotiateBind(INegotiationContext context) throws ConnectionException, NegotiationException {
            Iq iq = new Iq(Iq.Type.SET);
//            iq.setFrom(JabberId.parse(name+"@"+config.getHost()));
            iq.setObject(new Bind(config.getResource()));

            context.write(processingFactory.translate(iq));

            Object response = processingFactory.parse(readResponse(DEFAULT_READ_RESPONSE_TIMEOUT));

            if (response instanceof Iq) {
                iq = (Iq) response;

                if (iq.getObject() instanceof Bind) {
                    Bind bind = (Bind) iq.getObject();

                    if (bind.getJid() != null) {
                        context.setAttribute(StandardStreamer.NEGOTIATION_KEY_BINDED_CHAT_ID, bind.getJid());
                    }
                }

                if (context.getAttribute(StandardStreamer.NEGOTIATION_KEY_BINDED_CHAT_ID) == null) {
                    throw new NegotiationException(this);
                }
            } else {
                processError((IError) response, context, processingFactory);
            }
        }

        private Bind findBind(List<Feature> features) {
            for (Feature feature : features) {
                if (feature instanceof Bind)
                    return (Bind) feature;
            }

            return null;
        }
    }
}
